
#if defined(WIN32) || defined(WIN64)
#pragma unmanaged
#endif

#include <Liberty3D/Library/AssetDepotFolder.h>

#include <Liberty3D/Library/IAssetDepotListener.h>

#include <Liberty3D/Library/AssetImporter.h>
#include <Liberty3D/Library/AssetCachedData.h>
#include <Liberty3D/Library/AssetBuildData.h>
#include <Liberty3D/Library/AssetFolder.h>
#include <Liberty3D/Library/Mutex/Local.h>
#include <Liberty3D/Library/Task.h>
#include <Liberty3D/Library/TaskScheduler.h>

#include <Liberty3D/Engine/ISerializer.h>
#include <Liberty3D/Engine/ISerializableFactory.h>

#include <Liberty3D/Engine/CacheCleaner.h>
#include <Liberty3D/Engine/Conversion.h>
#include <Liberty3D/Engine/Path.h>
#include <Liberty3D/Engine/XML/Parser.h>
#include <Liberty3D/Engine/XML/Serializer.h>
#include <Liberty3D/Engine/XML/INode.h>

#include <algorithm>
#include <streambuf>
#include <fstream>
#include <sstream>

using namespace Liberty::Engine;
using namespace Liberty::Engine::Xml;
using namespace Liberty::Library;

AssetDepotFolder::AssetDepotFolder(const std::string& folder) : 
    AssetFileFolder(folder),

    _entryMutex          (new Mutex::Impl::Local::SharedMutex()),
    _root                (new AssetDepotEntryFolder()),
    _libraryDirty        (false),
    _detectChangesRunning(false),
    _revision            (0),

    _importerCache(ImporterCache(nullptr)),
    _dirty(Processor(), Queuer(nullptr))
{
    _dirty.queuer().folder = this;
    _importerCache.provider().folder = this;
}

AssetDepotFolder::~AssetDepotFolder() {
}

void AssetDepotFolder::init(bool create) {
    if (create) {
        makeDirectory("Assets",  true);
        makeDirectory("Library", true);
    }
    else {
        if (!exists("Assets")) {
            throw std::runtime_error("Assets folder doesn't exist.");
        }
        
        if (!exists("Library")) {
            throw std::runtime_error("Library folder doesn't exist.");
        }
    }

    if (exists(".liberty.database.xml")) {
        load();
    }
    else {
        dirtyLibraryLocked();
    }
}

void AssetDepotFolder::addListener(const std::shared_ptr<IAssetDepotListener>& l) {
    _listeners.push_back(l);
}

void AssetDepotFolder::removeListener(const std::shared_ptr<IAssetDepotListener>& l) {
    AssetDepotListenerVector::iterator it = std::find(_listeners.begin(), _listeners.end(), l);

    if (it != _listeners.end()) {
        _listeners.erase(it);
    }
}

void AssetDepotFolder::buildMap() {
    std::unique_ptr<IExclusiveLock> guard(_entryMutex->lock());

    // Rebuild the uuid -> AssetDepotEntry map.
    _map.clear();
    buildMap(_root);
}

void AssetDepotFolder::buildMap(const std::shared_ptr<AssetDepotEntryFolder>& folder) {
    AssetDepotEntryFolder::EntryList::const_iterator it, end = folder->childs.end();

    for (it = folder->childs.begin(); it != end; ++it) {
        const std::shared_ptr<AssetDepotEntry>& entry(*it);

        if (_map.find(entry->uuid) != _map.end()) {
            duplicateId(nullptr, entry);
        }

        _map.insert(std::pair<Uuid, std::shared_ptr<AssetDepotEntry>>(entry->uuid, entry));

        std::shared_ptr<AssetDepotEntryFolder> subFolder(std::dynamic_pointer_cast<AssetDepotEntryFolder>(entry));

        if (subFolder) {
            buildMap(subFolder);
        }
    }
}

std::string AssetDepotFolder::pathForUuid(const Liberty::Engine::Uuid& uuid) const {
    std::unique_ptr<ISharedLock>       guard(_entryMutex->lockShared());
    AssetDepotEntryMap::const_iterator it(_map.find(uuid));

    if (it != _map.end()) {
        return (*it).second->projectPath();
    }

    return std::string();
}

Uuid AssetDepotFolder::uuidForPath(const std::string& fileName) const {
    std::vector<std::string>     parts(Path::splitParts(fileName));
    std::unique_ptr<ISharedLock> guard(_entryMutex->lockShared());

    std::vector<std::string>::const_iterator it, end(parts.end());

    std::shared_ptr<AssetDepotEntry> current(_root);

    for (it = parts.begin(); it != end; ++it) {
        std::shared_ptr<AssetDepotEntryFolder> currentFolder(std::dynamic_pointer_cast<AssetDepotEntryFolder>(current));

        if (!currentFolder) {
            return Uuid();
        }

        current = currentFolder->find(*it);

        if (!current) {
            return Uuid();
        }
    }

    if (current) {
        return current->uuid;
    }

    return Uuid();
}

std::shared_ptr<AssetImporter> AssetDepotFolder::importer(const Liberty::Engine::Uuid& uuid) {
    return _importerCache.obtain(uuid);
}

std::shared_ptr<AssetCachedData> AssetDepotFolder::cachedData(const Liberty::Engine::Uuid& uuid) {
    return std::shared_ptr<AssetCachedData>();
}

std::shared_ptr<AssetBuildData> AssetDepotFolder::buildData(const Liberty::Engine::Uuid& uuid, const std::string& buildTag) {
    return std::shared_ptr<AssetBuildData>();
}

std::list<std::string> AssetDepotFolder::buildDatas(const Liberty::Engine::Uuid& uuid) {
    throw std::runtime_error("TODO");
}

void AssetDepotFolder::reimport(const Liberty::Engine::Uuid& uuid) {
    //std::unique_ptr<IExclusiveLock> guard(_cacheMutex->lock());

    {
        std::string p(cachedDataPath(uuid));

        if (AssetDepotFolder::exists(p)) {
            AssetDepotFolder::remove(p, true);
        }

        p = buildDataPath(uuid);

        if (AssetDepotFolder::exists(p)) {
            AssetDepotFolder::remove(p, true);
        }
    }

    throw std::runtime_error("UUID " + uuid.toString() + " doesn't exist");
}

void AssetDepotFolder::dirty(const std::shared_ptr<AssetImporter>& dirty) {
    _dirty.queue(std::static_pointer_cast<MappedAsset>(dirty));
}

void AssetDepotFolder::dirty(const std::shared_ptr<AssetCachedData>& dirty) {
    _dirty.queue(std::static_pointer_cast<MappedAsset>(dirty));
}

void AssetDepotFolder::dirty(const std::shared_ptr<AssetBuildData>& dirty) {
    throw std::runtime_error("TODO");
}

std::string AssetDepotFolder::importerPath(const Liberty::Engine::Uuid& uuid, std::string& path) const {
    path = pathForUuid(uuid);

    std::string metaPath;
    
    metaPath.reserve(path.length() + 5);
    metaPath += path;
    metaPath += ".meta";
    return metaPath;
}

std::string AssetDepotFolder::cachedDataPath(const Liberty::Engine::Uuid& uuid) const {
    std::string name(".liberty.cache/");
    name += uuid.toString();
    name.insert(name.begin() + 17, '/');
    return name;
}

std::string AssetDepotFolder::buildDataPath(const Liberty::Engine::Uuid& uuid) const {
    std::string name(".liberty.build/");
    name += uuid.toString();
    name.insert(name.begin() + 17, '/');
    return name;
}

std::string AssetDepotFolder::buildDataPath(const Liberty::Engine::Uuid& uuid, const std::string& buildTag) const {
    std::string name(".liberty.build/");
    name += uuid.toString();
    name.insert(name.begin() + 17, '/');
    name += "/";
    name += buildTag;
    return name;
}

AssetDepotFolder::ImporterCache::ImporterCache(AssetDepotFolder* f) : folder(f) {
}

std::shared_ptr<AssetImporter> AssetDepotFolder::ImporterCache::create(const Liberty::Engine::Uuid& uuid, bool& created) {
    std::string path    (folder->pathForUuid(uuid));
    std::string metaPath(path);

    metaPath += ".meta";

    std::streambuf* buf = folder->open(metaPath, std::ios::in);

    std::shared_ptr<AssetImporter> importer;
    ImporterFactory                factory(folder);

    if (buf == nullptr) {
        created = true;

        std::string extension;

        Path::splitExtension(path, &extension);
        importer = std::static_pointer_cast<AssetImporter>(factory.create(AssetImporter::classNameForExtension(extension.c_str())));

        if (!importer) {
            // If no default importer can't be found, let's create the default one.
            importer = std::shared_ptr<AssetImporter>(new AssetFolder<AssetImporter>(folder->shared_from_this()));
        }

        std::shared_ptr<IAssetFolder> importerFolder(std::dynamic_pointer_cast<IAssetFolder>(importer));
        
        importerFolder->__folderSetStatus(ModifiedInMemory);
        importerFolder->__folderSetUuid(uuid);
        return importer;
    }
    else {
        created = false;

        std::shared_ptr<INode> node(Parser::parse(buf));

        if (node) {
        }

        throw std::runtime_error("TODO");
    }
}

void AssetDepotFolder::ImporterCache::dirty(const std::shared_ptr<AssetImporter>& importer) {
    folder->_dirty.queue(importer);
}

std::shared_ptr<AssetCachedData> AssetDepotFolder::createCachedData(const Liberty::Engine::Uuid& uuid, bool& created) {
    std::string name(".liberty.cache/");
    name += uuid.toString();
    name.insert(name.begin() + 17, '/');

    throw std::runtime_error("TODO");
}

std::shared_ptr<AssetBuildData> AssetDepotFolder::createBuildData(const Liberty::Engine::Uuid& uuid, const std::string& buildTag, bool& created) {
    std::string name(".liberty.build/");
    name += uuid.toString();
    name.insert(name.begin() + 17, '/');
    name += "/";
    name += buildTag;

    throw std::runtime_error("TODO");
}

bool AssetDepotFolder::exists(const std::string& resourceName) const {
    return Liberty::Engine::AssetFileFolder::exists(resourceName);
}

std::streambuf* AssetDepotFolder::open(const std::string& resourceName, std::ios::openmode mode) const {
    return AssetFileFolder::open(resourceName, mode);
}

void AssetDepotFolder::list(const std::string& folder, std::vector<std::string>& entries) const {
    return AssetFileFolder::list(folder, entries);
}

void AssetDepotFolder::makeDirectory(const std::string& folder, bool createIntermediates) {
    AssetFileFolder::makeDirectory(folder, createIntermediates);
}

void AssetDepotFolder::remove(const std::string& path, bool recursive) {
    AssetFileFolder::remove(path, recursive);
}

void AssetDepotFolder::sync() {
    AssetFileFolder::sync();
}

void AssetDepotFolder::onMemoryLow() {
/*
    std::unique_ptr<IExclusiveLock> exclusiveGuard(_cacheMutex->lock());

    EraseExpired(_cache);*/
}

void AssetDepotFolder::onMemoryGarbageCollect() {
/*
    std::unique_ptr<IExclusiveLock> exclusiveGuard(_cacheMutex->lock());

    EraseExpired(_cache); */
}

AssetDepotFolder::Queuer::Queuer(AssetDepotFolder* f) : folder(f) {
}

void AssetDepotFolder::Queuer::operator()() {
    TaskScheduler::scheduleMethod<void, AssetDepotFolder>(folder->shared_from_this(), &AssetDepotFolder::processDirtyAssets);
}

void AssetDepotFolder::processDirtyAssets() {
    _dirty.process();
}

std::shared_ptr<IAssetDepot> IAssetDepot::assetDepotForFolder(const std::string& folder, bool create) {
    std::shared_ptr<AssetDepotFolder> depot(new AssetDepotFolder(folder));

    depot->init(create);
    return depot;
}
